; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s

declare void @llvm.assume(i1 noundef)

define i1 @gep_sub_1_uge_inbounds(ptr %dst, ptr %lower) {
; CHECK-LABEL: @gep_sub_1_uge_inbounds(
; CHECK-NEXT:    [[PRE:%.*]] = icmp uge ptr [[DST:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp uge ptr [[DST_SUB_1]], [[LOWER]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp uge ptr [[DST_SUB_3]], [[LOWER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 true, true
; CHECK-NEXT:    [[DST_SUB_4:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -4
; CHECK-NEXT:    [[CMP_SUB_4:%.*]] = icmp uge ptr [[DST_SUB_4]], [[LOWER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_4]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %pre = icmp uge ptr %dst, %lower
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr inbounds i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp uge ptr %dst.sub.1, %lower
  %dst.sub.3 = getelementptr inbounds i8, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp uge ptr %dst.sub.3, %lower
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.3
  %dst.sub.4 = getelementptr inbounds i8, ptr %dst.add.3, i64 -4
  %cmp.sub.4 = icmp uge ptr %dst.sub.4, %lower
  %res.2 = xor i1 %res.1, %cmp.sub.4
  ret i1 %res.2
}

define i1 @gep_sub_1_uge_only_inner_inbounds(ptr %dst, ptr %lower) {
; CHECK-LABEL: @gep_sub_1_uge_only_inner_inbounds(
; CHECK-NEXT:    [[PRE:%.*]] = icmp uge ptr [[DST:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp uge ptr [[DST_SUB_1]], [[LOWER]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp uge ptr [[DST_SUB_3]], [[LOWER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 [[CMP_SUB_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    [[DST_SUB_4:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -4
; CHECK-NEXT:    [[CMP_SUB_4:%.*]] = icmp uge ptr [[DST_SUB_4]], [[LOWER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_4]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %pre = icmp uge ptr %dst, %lower
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr inbounds i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr i8, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp uge ptr %dst.sub.1, %lower
  %dst.sub.3 = getelementptr i8, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp uge ptr %dst.sub.3, %lower
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.3
  %dst.sub.4 = getelementptr i8, ptr %dst.add.3, i64 -4
  %cmp.sub.4 = icmp uge ptr %dst.sub.4, %lower
  %res.2 = xor i1 %res.1, %cmp.sub.4
  ret i1 %res.2
}

define i1 @gep_sub_1_uge_only_outer_inbounds(ptr %dst, ptr %lower) {
; CHECK-LABEL: @gep_sub_1_uge_only_outer_inbounds(
; CHECK-NEXT:    [[PRE:%.*]] = icmp uge ptr [[DST:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp uge ptr [[DST_SUB_1]], [[LOWER]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp uge ptr [[DST_SUB_3]], [[LOWER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 [[CMP_SUB_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    [[DST_SUB_4:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -4
; CHECK-NEXT:    [[CMP_SUB_4:%.*]] = icmp uge ptr [[DST_SUB_4]], [[LOWER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_4]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %pre = icmp uge ptr %dst, %lower
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp uge ptr %dst.sub.1, %lower
  %dst.sub.3 = getelementptr inbounds i8, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp uge ptr %dst.sub.3, %lower
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.3
  %dst.sub.4 = getelementptr inbounds i8, ptr %dst.add.3, i64 -4
  %cmp.sub.4 = icmp uge ptr %dst.sub.4, %lower
  %res.2 = xor i1 %res.1, %cmp.sub.4
  ret i1 %res.2
}

define i1 @gep_sub_1_uge_no_inbounds(ptr %dst, ptr %lower) {
; CHECK-LABEL: @gep_sub_1_uge_no_inbounds(
; CHECK-NEXT:    [[PRE:%.*]] = icmp uge ptr [[DST:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp uge ptr [[DST_SUB_1]], [[LOWER]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp uge ptr [[DST_SUB_3]], [[LOWER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 [[CMP_SUB_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    [[DST_SUB_4:%.*]] = getelementptr i8, ptr [[DST_ADD_3]], i64 -4
; CHECK-NEXT:    [[CMP_SUB_4:%.*]] = icmp uge ptr [[DST_SUB_4]], [[LOWER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_4]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %pre = icmp uge ptr %dst, %lower
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr i8, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp uge ptr %dst.sub.1, %lower
  %dst.sub.3 = getelementptr i8, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp uge ptr %dst.sub.3, %lower
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.3
  %dst.sub.4 = getelementptr i8, ptr %dst.add.3, i64 -4
  %cmp.sub.4 = icmp uge ptr %dst.sub.4, %lower
  %res.2 = xor i1 %res.1, %cmp.sub.4
  ret i1 %res.2
}

define i1 @gep_sub_1_ult(ptr %dst, ptr %upper) {
; CHECK-LABEL: @gep_sub_1_ult(
; CHECK-NEXT:    [[DST_ADD_4:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i64 4
; CHECK-NEXT:    [[PRE:%.*]] = icmp ult ptr [[DST_ADD_4]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp ult ptr [[DST_SUB_3]], [[UPPER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 true, true
; CHECK-NEXT:    ret i1 [[RES_1]]
;
  %dst.add.4 = getelementptr inbounds i8, ptr %dst, i64 4
  %pre = icmp ult ptr %dst.add.4, %upper
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr inbounds i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  %dst.sub.3 = getelementptr inbounds i8, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp ult ptr %dst.sub.3, %upper
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.3
  ret i1 %res.1
}

define i1 @gep_sub_ult_var_idx(ptr %dst, ptr %upper, i8 %idx) {
; CHECK-LABEL: @gep_sub_ult_var_idx(
; CHECK-NEXT:    [[NOT_ZERO:%.*]] = icmp ne i8 [[IDX:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT_ZERO]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[PRE:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_IDX]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER]]
; CHECK-NEXT:    [[DST_SUB_2:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_IDX]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_2:%.*]] = icmp ult ptr [[DST_SUB_2]], [[UPPER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 true, [[CMP_SUB_2]]
; CHECK-NEXT:    [[DST_SUB_1_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_SUB_1]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1_SUB_1]], [[UPPER]]
; CHECK-NEXT:    [[CMP_SUB_1_SUB_1_EQ:%.*]] = icmp eq ptr [[DST_SUB_1_SUB_1]], [[DST_SUB_2]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_1_SUB_1]]
; CHECK-NEXT:    [[RES_3:%.*]] = xor i1 [[RES_2]], [[CMP_SUB_1_SUB_1_EQ]]
; CHECK-NEXT:    ret i1 [[RES_3]]
;
  %not.zero = icmp ne i8 %idx, 0
  call void @llvm.assume(i1 %not.zero)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %pre = icmp ult ptr %dst.add.idx, %upper
  call void @llvm.assume(i1 %pre)
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.idx, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  %dst.sub.2 = getelementptr inbounds i8, ptr %dst.add.idx, i64 -2
  %cmp.sub.2 = icmp ult ptr %dst.sub.2, %upper
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.2
  %dst.sub.1.sub.1 = getelementptr inbounds i8, ptr %dst.sub.1, i64 -1
  %cmp.sub.1.sub.1 = icmp ult ptr %dst.sub.1.sub.1, %upper
  %cmp.sub.1.sub.1.eq = icmp eq ptr %dst.sub.1.sub.1, %dst.sub.2
  %res.2 = xor i1 %res.1, %cmp.sub.1.sub.1
  %res.3 = xor i1 %res.2, %cmp.sub.1.sub.1.eq
  ret i1 %res.3
}

define i1 @gep_sub_ult_var_idx_sgt_1(ptr %dst, ptr %upper, i8 %idx) {
; CHECK-LABEL: @gep_sub_ult_var_idx_sgt_1(
; CHECK-NEXT:    [[SGT_1:%.*]] = icmp sgt i8 [[IDX:%.*]], 1
; CHECK-NEXT:    call void @llvm.assume(i1 [[SGT_1]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[PRE:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_IDX]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER]]
; CHECK-NEXT:    [[DST_SUB_2:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_IDX]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_2:%.*]] = icmp ult ptr [[DST_SUB_2]], [[UPPER]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 [[CMP_SUB_1]], [[CMP_SUB_2]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_IDX]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp ult ptr [[DST_SUB_3]], [[UPPER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %sgt.1 = icmp sgt i8 %idx, 1
  call void @llvm.assume(i1 %sgt.1)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %pre = icmp ult ptr %dst.add.idx, %upper
  call void @llvm.assume(i1 %pre)
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.idx, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  %dst.sub.2 = getelementptr inbounds i8, ptr %dst.add.idx, i64 -2
  %cmp.sub.2 = icmp ult ptr %dst.sub.2, %upper
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.2
  %dst.sub.3 = getelementptr inbounds i8, ptr %dst.add.idx, i64 -3
  %cmp.sub.3 = icmp ult ptr %dst.sub.3, %upper
  %res.2 = xor i1 %res.1, %cmp.sub.3
  ret i1 %res.2
}

define i1 @gep_sub_1_ult_var_idx_inbounds(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_1_ult_var_idx_inbounds(
; CHECK-NEXT:    [[NOT_ZERO:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT_ZERO]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_LEN]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 true
;
  %not.zero = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %not.zero)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.len, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_1_ult_var_idx_only_inner_inbounds(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_1_ult_var_idx_only_inner_inbounds(
; CHECK-NEXT:    [[NOT_ZERO:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT_ZERO]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr i8, ptr [[DST_ADD_LEN]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 [[CMP_IDX]]
;
  %not.zero = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %not.zero)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr i8, ptr %dst.add.len, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_1_ult_var_idx_no_inbounds(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_1_ult_var_idx_no_inbounds(
; CHECK-NEXT:    [[NOT_ZERO:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT_ZERO]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr i8, ptr [[DST_ADD_LEN]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 [[CMP_IDX]]
;
  %not.zero = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %not.zero)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr i8, ptr %dst.add.len, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_2_ult_var_idx(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_2_ult_var_idx(
; CHECK-NEXT:    [[NOT_ZERO:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT_ZERO]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_LEN]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 [[CMP_IDX]]
;
  %not.zero = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %not.zero)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.len, i64 -2
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_2_ult_var_idx_inbounds_len_sge_2(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_2_ult_var_idx_inbounds_len_sge_2(
; CHECK-NEXT:    [[SGE_2:%.*]] = icmp sge i8 [[LEN:%.*]], 2
; CHECK-NEXT:    call void @llvm.assume(i1 [[SGE_2]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_LEN]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 true
;
  %sge.2 = icmp sge i8 %len, 2
  call void @llvm.assume(i1 %sge.2)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.len, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_2_ult_var_idx_inbounds_len_uge_2(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_2_ult_var_idx_inbounds_len_uge_2(
; CHECK-NEXT:    [[UGE_2:%.*]] = icmp uge i8 [[LEN:%.*]], 2
; CHECK-NEXT:    call void @llvm.assume(i1 [[UGE_2]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_LEN]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 true
;
  %uge.2 = icmp uge i8 %len, 2
  call void @llvm.assume(i1 %uge.2)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.len, i64 -1
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_ult_var_idx_len_sgt_1(ptr %dst, ptr %upper, i8 %len, i8 %idx) {
; CHECK-LABEL: @gep_sub_ult_var_idx_len_sgt_1(
; CHECK-NEXT:    [[SGT_1:%.*]] = icmp sgt i8 [[LEN:%.*]], 1
; CHECK-NEXT:    call void @llvm.assume(i1 [[SGT_1]])
; CHECK-NEXT:    [[LEN_EXT:%.*]] = zext i8 [[LEN]] to i16
; CHECK-NEXT:    [[DST_ADD_LEN:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i16 [[LEN_EXT]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[DST_ADD_LEN]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ult ptr [[DST_SUB_1]], [[UPPER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_SUB_1]])
; CHECK-NEXT:    [[CMP_IDX_ULT_LEN:%.*]] = icmp ult i8 [[IDX:%.*]], [[LEN]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_IDX_ULT_LEN]])
; CHECK-NEXT:    [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i16
; CHECK-NEXT:    [[DST_ADD_IDX:%.*]] = getelementptr inbounds i8, ptr [[DST]], i16 [[IDX_EXT]]
; CHECK-NEXT:    [[CMP_IDX:%.*]] = icmp ult ptr [[DST_ADD_IDX]], [[UPPER]]
; CHECK-NEXT:    ret i1 [[CMP_IDX]]
;
  %sgt.1 = icmp sgt i8 %len, 1
  call void @llvm.assume(i1 %sgt.1)
  %len.ext = zext i8 %len to i16
  %dst.add.len = getelementptr inbounds i8, ptr %dst, i16 %len.ext
  %dst.sub.1 = getelementptr inbounds i8, ptr %dst.add.len, i64 -2
  %cmp.sub.1 = icmp ult ptr %dst.sub.1, %upper
  call void @llvm.assume(i1 %cmp.sub.1)
  %cmp.idx.ult.len = icmp ult i8 %idx, %len
  call void @llvm.assume(i1 %cmp.idx.ult.len)
  %idx.ext = zext i8 %idx to i16
  %dst.add.idx = getelementptr inbounds i8, ptr %dst, i16 %idx.ext
  %cmp.idx = icmp ult ptr %dst.add.idx, %upper
  ret i1 %cmp.idx
}

define i1 @gep_sub_1_ult_var_idx_lower_bound(ptr %lower, ptr %src, i8 %len) {
; CHECK-LABEL: @gep_sub_1_ult_var_idx_lower_bound(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[SRC_UGE_LOWER:%.*]] = icmp uge ptr [[SRC:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[SRC_UGE_LOWER]])
; CHECK-NEXT:    [[LEN_POS:%.*]] = icmp sge i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[LEN_POS]])
; CHECK-NEXT:    [[GEP_LEN:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i8 [[LEN]]
; CHECK-NEXT:    [[GEP_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[GEP_LEN]], i8 -1
; CHECK-NEXT:    [[RES:%.*]] = icmp ult ptr [[GEP_SUB_1]], [[LOWER]]
; CHECK-NEXT:    ret i1 [[RES]]
;
entry:
  %src.uge.lower = icmp uge ptr %src, %lower
  call void @llvm.assume(i1 %src.uge.lower)

  %len.pos = icmp sge i8 %len, 0
  call void @llvm.assume(i1 %len.pos)

  %gep.len = getelementptr inbounds i8, ptr %src, i8 %len
  %gep.sub.1 = getelementptr inbounds i8, ptr %gep.len, i8 -1
  %res = icmp ult ptr %gep.sub.1, %lower
  ret i1 %res
}

define i1 @gep_sub_1_ult_var_idx_lower_bound_len_ne_0(ptr %lower, ptr %src, i8 %len) {
; CHECK-LABEL: @gep_sub_1_ult_var_idx_lower_bound_len_ne_0(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[LEN_NE_0:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[LEN_NE_0]])
; CHECK-NEXT:    [[SRC_UGE_LOWER:%.*]] = icmp uge ptr [[SRC:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[SRC_UGE_LOWER]])
; CHECK-NEXT:    [[LEN_POS:%.*]] = icmp sge i8 [[LEN]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[LEN_POS]])
; CHECK-NEXT:    [[GEP_LEN:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i8 [[LEN]]
; CHECK-NEXT:    [[GEP_SUB_1:%.*]] = getelementptr inbounds i8, ptr [[GEP_LEN]], i8 -1
; CHECK-NEXT:    [[RES:%.*]] = icmp ult ptr [[GEP_SUB_1]], [[LOWER]]
; CHECK-NEXT:    ret i1 [[RES]]
;
entry:
  %len.ne.0 = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %len.ne.0)

  %src.uge.lower = icmp uge ptr %src, %lower
  call void @llvm.assume(i1 %src.uge.lower)

  %len.pos = icmp sge i8 %len, 0
  call void @llvm.assume(i1 %len.pos)

  %gep.len = getelementptr inbounds i8, ptr %src, i8 %len
  %gep.sub.1 = getelementptr inbounds i8, ptr %gep.len, i8 -1
  %res = icmp ult ptr %gep.sub.1, %lower
  ret i1 %res
}

define i1 @gep_sub_2_ult_var_idx_lower_bound_len_ne_0(ptr %lower, ptr %src, i8 %len) {
; CHECK-LABEL: @gep_sub_2_ult_var_idx_lower_bound_len_ne_0(
; CHECK-NEXT:  entry:
; CHECK-NEXT:    [[LEN_NE_0:%.*]] = icmp ne i8 [[LEN:%.*]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[LEN_NE_0]])
; CHECK-NEXT:    [[SRC_UGE_LOWER:%.*]] = icmp uge ptr [[SRC:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[SRC_UGE_LOWER]])
; CHECK-NEXT:    [[LEN_POS:%.*]] = icmp sge i8 [[LEN]], 0
; CHECK-NEXT:    call void @llvm.assume(i1 [[LEN_POS]])
; CHECK-NEXT:    [[GEP_LEN:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i8 [[LEN]]
; CHECK-NEXT:    [[GEP_SUB_2:%.*]] = getelementptr inbounds i8, ptr [[GEP_LEN]], i8 -2
; CHECK-NEXT:    [[RES:%.*]] = icmp ult ptr [[GEP_SUB_2]], [[LOWER]]
; CHECK-NEXT:    ret i1 [[RES]]
;
entry:
  %len.ne.0 = icmp ne i8 %len, 0
  call void @llvm.assume(i1 %len.ne.0)

  %src.uge.lower = icmp uge ptr %src, %lower
  call void @llvm.assume(i1 %src.uge.lower)

  %len.pos = icmp sge i8 %len, 0
  call void @llvm.assume(i1 %len.pos)

  %gep.len = getelementptr inbounds i8, ptr %src, i8 %len
  %gep.sub.2 = getelementptr inbounds i8, ptr %gep.len, i8 -2
  %res = icmp ult ptr %gep.sub.2, %lower
  ret i1 %res
}

define i1 @gep_i16_sub_1_uge_inbounds(ptr %dst, ptr %lower) {
; CHECK-LABEL: @gep_i16_sub_1_uge_inbounds(
; CHECK-NEXT:    [[PRE:%.*]] = icmp uge ptr [[DST:%.*]], [[LOWER:%.*]]
; CHECK-NEXT:    call void @llvm.assume(i1 [[PRE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 3
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i64 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ule ptr [[DST_SUB_1]], [[LOWER]]
; CHECK-NEXT:    [[DST_SUB_2:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_2:%.*]] = icmp ule ptr [[DST_SUB_2]], [[DST]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 false, [[CMP_SUB_2]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp ule ptr [[DST_SUB_3]], [[LOWER]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %pre = icmp uge ptr %dst, %lower
  call void @llvm.assume(i1 %pre)
  %dst.add.3 = getelementptr inbounds i8, ptr %dst, i64 3
  %dst.sub.1 = getelementptr inbounds i16, ptr %dst.add.3, i64 -1
  %cmp.sub.1 = icmp ule ptr %dst.sub.1, %lower
  %dst.sub.2 = getelementptr inbounds i16, ptr %dst.add.3, i64 -2
  %cmp.sub.2 = icmp ule ptr %dst.sub.2, %dst
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.2
  %dst.sub.3 = getelementptr inbounds i16, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp ule ptr %dst.sub.3, %lower
  %res.2 = xor i1 %res.1, %cmp.sub.3
  ret i1 %res.2
}

define i1 @gep_i16_sub_1_uge_inbounds_var_idx(ptr %dst, i64 %off) {
; CHECK-LABEL: @gep_i16_sub_1_uge_inbounds_var_idx(
; CHECK-NEXT:    [[OFF_UGE:%.*]] = icmp sge i64 [[OFF:%.*]], 1
; CHECK-NEXT:    call void @llvm.assume(i1 [[OFF_UGE]])
; CHECK-NEXT:    [[DST_ADD_3:%.*]] = getelementptr inbounds i8, ptr [[DST:%.*]], i64 [[OFF]]
; CHECK-NEXT:    [[DST_SUB_1:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i32 -1
; CHECK-NEXT:    [[CMP_SUB_1:%.*]] = icmp ule ptr [[DST_SUB_1]], [[DST]]
; CHECK-NEXT:    [[DST_SUB_2:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i64 -2
; CHECK-NEXT:    [[CMP_SUB_2:%.*]] = icmp ule ptr [[DST_SUB_2]], [[DST]]
; CHECK-NEXT:    [[RES_1:%.*]] = xor i1 [[CMP_SUB_1]], [[CMP_SUB_2]]
; CHECK-NEXT:    [[DST_SUB_3:%.*]] = getelementptr inbounds i16, ptr [[DST_ADD_3]], i64 -3
; CHECK-NEXT:    [[CMP_SUB_3:%.*]] = icmp ule ptr [[DST_SUB_3]], [[DST]]
; CHECK-NEXT:    [[RES_2:%.*]] = xor i1 [[RES_1]], [[CMP_SUB_3]]
; CHECK-NEXT:    ret i1 [[RES_2]]
;
  %off.uge = icmp sge i64 %off, 1
  call void @llvm.assume(i1 %off.uge)
  %dst.add.3 = getelementptr inbounds i8, ptr %dst, i64 %off
  %dst.sub.1 = getelementptr inbounds i16, ptr %dst.add.3, i32 -1
  %cmp.sub.1 = icmp ule ptr %dst.sub.1, %dst
  %dst.sub.2 = getelementptr inbounds i16, ptr %dst.add.3, i64 -2
  %cmp.sub.2 = icmp ule ptr %dst.sub.2, %dst
  %res.1 = xor i1 %cmp.sub.1, %cmp.sub.2
  %dst.sub.3 = getelementptr inbounds i16, ptr %dst.add.3, i64 -3
  %cmp.sub.3 = icmp ule ptr %dst.sub.3, %dst
  %res.2 = xor i1 %res.1, %cmp.sub.3
  ret i1 %res.2
}
